//
// Created by ouka on 2021/12/17.
//

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "ctype.h"
#include "stdarg.h"
#include "StringUtil.h"
static char* newString2(int,...);
static char* addExtra(char* p1, char* p2);
static char* add(char* p1, char* p2);
static char* newString(int num, ...);
static void  delString(char* p);
static int   split(char* buff, char* separator, Array_t* result);
static int   splitExtra(char* buff, char* separator, Array_t* result);
static void  delArray(Array_t p, int n);
static char* toUpper(char* ptr);
static char* toLower(char* ptr);
static Bool  startWith(char* src, char* str);
static Bool  endWith(char* src, char* str);
static char* join(Array_t ptr, int n);
static char* strip(char* ptr, char* separator);
OUKA oukalala = {
        .newString2=newString2
};
OUKA ouka2 = {
        .newString2=NULL
};

STRINGUTIL StringUtil = {.add=add,
        .addExtra=addExtra,
        .newString=newString,
        .delString=delString,
        .split=split,
        .splitExtra=splitExtra,
        .delArray=delArray,
        .toUpper=toUpper,
        .toLower=toLower,
        .startWith=startWith,
        .endWith=endWith,
        .join=join,
        .strip=strip
};
static char* newString2(int num,...)
{
    return NULL;
}
/**
 * @description: 字符串合并
 * @param {p1} 字符串1
 * @param {p2} 字符串2
 * @return {*} 合并后的字符串指针p1
 * @attention 会释放p1，所以调用时等号左边要有，不能省略，否则用的是已经释放的旧地址。如：str = stringUtil.add(p1,p2)
 */
static char* addExtra(char* p1, char* p2)
{
    char* ptr = NULL;
    ptr = (char*)realloc(p1, strlen(p1)+strlen(p2)+1);
    if (ptr != NULL) {
        strcat(ptr, p2);
    }
    return ptr;
}

/**
 * @description: 字符串合并
 * @param {p1} 字符串1
 * @param {p2} 字符串2
 * @return {*} 合并后的字符串指针p1
 * @attention 会创建一个新的字符串返回
 */
static char* add(char* p1, char* p2)
{
    char* ptr = NULL;
    ptr = (char*)calloc(strlen(p1)+strlen(p2)+1, 1);
    if (ptr != NULL) {
        strcat(ptr, p1);
        strcat(ptr, p2);
    }
    return ptr;
}

/**
 * @description: 创建字符串
 * @param {num} 字符串数组的个数
 * @param {...} 多个字符串数组
 * @return {*} 创建完的字符串指针
 * @attention 需要调用delString()手动释放ptr
 */
static char* newString(int num, ...)
{
    char *arg = NULL;
    va_list ap;
    int length = 0;
    va_start(ap, num);
    for (int i = 0; i < num; i++) {
        arg = va_arg(ap, char*);
        length += strlen(arg);
    }
    // va_end(ap);
    char* ptr = (char*)calloc(length+1, sizeof(char));
    if (ptr != NULL) {
        va_start(ap, num);
        for (int i = 0; i < num; i++) {
            arg = va_arg(ap, char*);
            strcat(ptr, arg);
        }
        va_end(ap);
    }else {
        printf("malloc failed\r\n");
    }
    return ptr;
}


/**
 * @description: 释放一维指针的内存
 * @param {p} 一维指针
 * @return {*} 无
 */
static void delString(char* p)
{
    free(p);
    p = NULL;
}

/**
 * @description: 释放二维指针的内存
 * @param {p} 二维指针
 * @param {n} 第一维的数量
 * @return {*} 无
 * @attention 使用本函数可释放调用split()函数后的二维指针的内存
 */
static void delArray(Array_t p, int n)
{
    for(int i=0; i<n; i++) {
        free(*(p+i));
    }
    free(p);
    p = NULL;
}

/**
 * @description: 分割字符串
 * @param {buff} 待分割的字符串指针
 * @param {separator} 分隔符
 * @param {result} 分割后的字符串数组
 * @return {*} 分割后的字符串数组的第一维的数量
 * @attention 会改变原字符串，需要调用delArray()手动释放result
 */
static int split(char* buff, char* separator, Array_t* result)
{
    int max = 4;
    int index = 0;
    *result = (Array_t)calloc(max, sizeof(char*));

    char *token;
    /* 获取第一个子字符串 */
    token = strtok(buff, separator);
    /* 继续获取其他的子字符串 */
    while( token != NULL ) {
        // printf( "%s\n", token);
        if(index == max) {
            max *= 2;
            *result = (Array_t)realloc(*result, max*sizeof(char*));
            if(*result == NULL) {
                printf("realloc failed\r\n");
                return 0;
            }else {
                // printf("realloc success\r\n");
            }
        }
        *(*result+index) = (char*)calloc(strlen(token)+1, sizeof(char));
        strcpy(*(*result+index), token);
        index ++;
        token = strtok(NULL, separator);
    }
    return index;
}

/**
 * @description: 分割字符串
 * @param {buff} 待分割的字符串指针
 * @param {separator} 分隔符
 * @param {result} 分割后的字符串数组
 * @return {*} 分割后的字符串数组的第一维的数量
 * @attention 不会改变原字符串，需要调用delArray()手动释放result
 */
static int splitExtra(char* buff, char* separator, Array_t* result)
{
    int max = 4;
    int index = 0;
    char *prev = buff;
    char *token;

    *result = (Array_t)calloc(max, sizeof(char*));
    /* 获取第一个子字符串 */
    token = strstr(buff, separator);
    /* 继续获取其他的子字符串 */
    while( token != NULL ) {
        // printf( "%s\n", token);
        if(index == max) {
            max *= 2;
            *result = (Array_t)realloc(*result, max*sizeof(char*));
            if(*result == NULL) {
                printf("realloc failed\r\n");
                return 0;
            }else {
                // printf("realloc success\r\n");
            }
        }
        *(*result+index) = (char*)calloc(token-prev+1, sizeof(char));
        if (*(*result+index) == NULL) {
            printf("calloc failed\r\n");
        }
        memcpy(*(*result+index), prev, token-prev);
        prev = token+strlen(separator);
        index ++;
        token = strstr(prev, separator);
    }
    /* 将最后部分加入 */
    if(index == max) {
        max += 1;
        *result = (Array_t)realloc(*result, max*sizeof(char*));
        if(*result == NULL) {
            printf("realloc failed\r\n");
            return 0;
        }else {
            // printf("realloc success\r\n");
        }
    }
    int remain = strlen(buff)-(prev-buff);
    *(*result+index) = (char*)calloc(remain+1, sizeof(char));
    if (*(*result+index) == NULL) {
        printf("calloc failed\r\n");
    }
    memcpy(*(*result+index), prev, remain);
    index ++;

    return index;
}

/**
 * @description: 字符串大写
 * @param {ptr} 原字符串
 * @return {*} 大写后得字符串
 * @attention 会改变原字符串
 */
static char* toUpper(char* ptr)
{
    char *orign = ptr;
    for (; *ptr != '\0'; ptr++) {
        *ptr = toupper(*ptr);
    }
    return orign;
}

/**
 * @description: 字符串小写
 * @param {ptr} 原字符串
 * @return {*} 小写后得字符串
 * @attention 会改变原字符串
 */
static char* toLower(char* ptr)
{
    char *orign = ptr;
    for (; *ptr != '\0'; ptr++) {
        *ptr = tolower(*ptr);
    }
    return orign;
}

/**
 * @description: 是否以指定子字符串开头
 * @param {src} 待比较的字符串
 * @param {str} 指定的子字符串
 * @return {*} true/false
 */
static Bool startWith(char* src, char* str)
{
    if (strlen(src) < strlen(str)) {
        return false;
    }
    for (int i = 0; i < strlen(str); i++) {
        if (src[i] != str[i]) {
            return false;
        }
    }
    return true;
}

/**
 * @description: 是否以指定子字符串结尾
 * @param {src} 待比较的字符串
 * @param {str} 指定的子字符串
 * @return {*} true/false
 */
static Bool endWith(char* src, char* str)
{
    if (strlen(src) < strlen(str)) {
        return false;
    }
    char* ptr = src+(strlen(src)-strlen(str));
    for (int i = 0; i < strlen(str); i++) {
        if (ptr[i] != str[i]) {
            return false;
        }
    }
    return true;
}

/**
 * @description: 将字符串数组合并为一个字符串
 * @param {ptr} 字符串数组
 * @param {n} 数组第一维的数量
 * @return {*} 合并后的字符串
 * @attention 需要调用delString()手动释放buff
 */
static char* join(Array_t ptr, int n)
{
    int length = 0;
    for (int i = 0; i < n; i++) {
        length += strlen(*(ptr+i));
    }
    char* buff = NULL;
    buff = (char*)calloc(length+1, sizeof(char));
    if (buff)
    {
        for (int i = 0; i < n; i++) {
            strcat(buff, *(ptr+i));
        }
    }
    return buff;
}

/**
 * @description: 移除字符串中指定的子字符串
 * @param {ptr} 原字符串指针
 * @param {ptr} 指定的子字符串指针
 * @return {*} 移除后的字符串指针，会改变原字符串
 */
static char* strip(char* ptr, char* separator)
{
    Array_t res_arr;
    int cnt = splitExtra(ptr, separator, &res_arr);
    char* res_str = join(res_arr, cnt);
    strcpy(ptr, res_str);
    delArray(res_arr, cnt);
    delString(res_str);
    return ptr;
}


void stringUtilTest()
{
    String str = StringUtil.newString(2, "ab", "cd");
    printf("1. new String (abc): %s\r\n", str);

    str = StringUtil.add(str, ",e,f,g,h");
    printf("2. add String (,e,f,g,h): %s\r\n", str);

    Array_t res;
    int cnt = StringUtil.splitExtra(str, ",", &res);
    printf("3. split String: ");
    for (int i = 0; i < cnt; i++) {
        printf("[%d]: %s  ", i, res[i]);
    }
    printf("\r\n");

    StringUtil.toUpper(str);
    printf("4. upper String: %s\r\n", str);

    StringUtil.toLower(str);
    printf("5. lower String: %s\r\n", str);

    Bool check = StringUtil.startWith(str, "abc");
    printf("6. startWith (abc): %d\r\n", check);

    check = StringUtil.endWith(str, ",h");
    printf("7. endWith (,h): %d\r\n", check);

    String joinstr = StringUtil.join(res, cnt);
    printf("8. join String: %s\r\n", joinstr);

    StringUtil.strip(str, ",");
    printf("9. strip String: %s\r\n", str);

    StringUtil.delString(joinstr);
    StringUtil.delArray(res, cnt);
    StringUtil.delString(str);
}